package com.zbkj.admin.boot.config;

import com.zbkj.admin.boot.common.error.ErrorType;
import com.zbkj.admin.boot.common.error.ErrorUtil;
import com.zbkj.admin.boot.common.error.MicaErrorEvent;
import com.zbkj.admin.boot.context.MicaRunnableWrapper;
import com.zbkj.admin.boot.launch.MicaProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.context.IMicaContext;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.boot.task.SimpleAsyncTaskExecutorCustomizer;
import org.springframework.boot.task.SimpleAsyncTaskSchedulerCustomizer;
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.ErrorHandler;

import java.lang.reflect.Method;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步处理
 *
 * @author L.cm
 */
@Slf4j
@EnableAsync
@EnableScheduling
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
public class MicaExecutorConfiguration implements AsyncConfigurer {
    private final IMicaContext micaContext;
    private final MicaProperties micaProperties;
    private final ApplicationEventPublisher publisher;

    @Bean
    @ConditionalOnThreading(Threading.VIRTUAL)
    public SimpleAsyncTaskExecutorCustomizer simpleAsyncTaskExecutorCustomizer() {
        return taskExecutor -> {
            taskExecutor.setThreadNamePrefix("async-task-");
            taskExecutor.setTaskDecorator(MicaRunnableWrapper::new);
            taskExecutor.setVirtualThreads(true);
        };
    }

    @Bean
    @ConditionalOnThreading(Threading.VIRTUAL)
    public SimpleAsyncTaskSchedulerCustomizer simpleAsyncTaskSchedulerCustomizer() {
        return taskExecutor -> {
            taskExecutor.setThreadNamePrefix("scheduler-task-");
            taskExecutor.setVirtualThreads(true);
        };
    }

    @Bean
    @ConditionalOnThreading(Threading.PLATFORM)
    public ThreadPoolTaskExecutorCustomizer taskExecutorCustomizer() {
        return taskExecutor -> {
            taskExecutor.setThreadNamePrefix("async-task-");
            taskExecutor.setTaskDecorator(MicaRunnableWrapper::new);
            taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        };
    }

    @Bean
    @ConditionalOnThreading(Threading.PLATFORM)
    public ThreadPoolTaskSchedulerCustomizer taskSchedulerCustomizer() {
        return taskExecutor -> {
            taskExecutor.setThreadNamePrefix("scheduler-task-");
            taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            taskExecutor.setErrorHandler(new MicaErrorHandler(micaContext, micaProperties, publisher));
        };
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MicaAsyncUncaughtExceptionHandler(micaContext, micaProperties, publisher);
    }

    @RequiredArgsConstructor
    private static class MicaAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
        private final IMicaContext micaContext;
        private final MicaProperties micaProperties;
        private final ApplicationEventPublisher eventPublisher;

        @Override
        public void handleUncaughtException(Throwable error, Method method, Object... params) {
            log.error("Unexpected exception occurred invoking async method: {}", method, error);
            MicaErrorEvent event = new MicaErrorEvent();
            event.setErrorType(ErrorType.ASYNC);
            // 服务信息、环境、异常类型
            event.setAppName(micaProperties.getName());
            event.setEnv(micaProperties.getEnv());
            event.setRequestId(micaContext.getRequestId());
            // 堆栈信息
            ErrorUtil.initErrorInfo(error, event);
            // 发布事件
            eventPublisher.publishEvent(event);
        }
    }

    @RequiredArgsConstructor
    private static class MicaErrorHandler implements ErrorHandler {
        private final IMicaContext micaContext;
        private final MicaProperties micaProperties;
        private final ApplicationEventPublisher eventPublisher;

        @Override
        public void handleError(Throwable error) {
            log.error("Unexpected scheduler exception", error);
            MicaErrorEvent event = new MicaErrorEvent();
            event.setErrorType(ErrorType.SCHEDULER);
            // 服务信息、环境、异常类型
            event.setAppName(micaProperties.getName());
            event.setEnv(micaProperties.getEnv());
            event.setRequestId(micaContext.getRequestId());
            // 堆栈信息
            ErrorUtil.initErrorInfo(error, event);
            // 发布事件
            eventPublisher.publishEvent(event);
        }
    }
}
